#include "mld_pch.hpp"
#include "mld_impl.hpp"

#include <boostext/util/messages.hpp>




// ---------------------------------------------------------------------------------------------------------------------------------
// We define our own assert, because we don't want to bring up an assertion dialog, since that allocates RAM. Our new assert
// simply declares a forced breakpoint.
// ---------------------------------------------------------------------------------------------------------------------------------

#ifdef WIN32
	#include <assert.h>
	#define m_assert(x) assert(x)
	#define m_break() assert(false)
#else
	COMPILE_ERROR(The mld requires an MSVC compiler)
#endif




// ---------------------------------------------------------------------------------------------------------------------------------
// Local functions only
// ---------------------------------------------------------------------------------------------------------------------------------

/* This function will strip away the path of the filename, if there is one and
   return a constant pointer towards the first sign of the filename
   so no additional space will allocated
   */
static const char* fpath(const char* filename)
{
	// look for the last '\\' in sourceFile
	// the function returns a valid pointer to '\\' in the string, if there is one
	// or it will return 0;
	const char* ptr = strrchr(filename, '\\');
	if(ptr)
		return ptr + 1;

	// it returned 0, so we look for the last '/' in the filename
	ptr = strrchr(filename, '/');

	// if there is one, just return the last part of the filename (that is, the file without the part)
	if(ptr)
		return ptr + 1;

	return filename;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * This function will format the given input to reproduce a good looking output: "file(line)::function"
 *
 * @warning   this function is not thread-safe, but there is only one instance of the mld per application,
 *            and this function will only be called during destructon, hence this is not a problem
 */
static const char* ownerString(const char* module, const char* sourceFile, uint32 line, const char* function)
{
	static char str[160];
	memset(str, 0, sizeof(str));
	sprintf_s(str, sizeof(str), "%s::%s(%05d)::%s", fpath(module), fpath(sourceFile), line, function);
	return str;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * This function will print 'value' as an unsigned decimal integer and somehow insert commatas.
 *
 * @warning   this function is not thread safe
 */
static const char* insertCommas(std::size_t value)
{
	static char str[30];
	memset(str, 0, sizeof(str));

	sprintf_s(str, sizeof(str), "%u", value);
	if(strlen(str) > 3)
	{
		memmove(&str[strlen(str)-3], &str[strlen(str)-4], 4);
		str[strlen(str) - 4] = ',';
	}

	if(strlen(str) > 7)
	{
		memmove(&str[strlen(str)-7], &str[strlen(str)-8], 8);
		str[strlen(str) - 8] = ',';
	}

	if(strlen(str) > 11)
	{
		memmove(&str[strlen(str)-11], &str[strlen(str)-12], 12);
		str[strlen(str) - 12] = ',';
	}

	return str;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * This function creates a nicely formatted string that contains a textual
 * representation of the given number, interpreted as a size in bytes.
 *
 * @warning   this function is not thread-safe
 */
static const char* memorySizeString(std::size_t size)
{
	static char str[1024];

	// Format and print the size (MByte, Kbyte, Byte)
	if(size > (1024*1024))
		sprintf_s(str, sizeof(str), "%10s (%7.2fM)", insertCommas(size), static_cast<float>(size) / (1024.0f * 1024.0f));
	else if(size > 1024)
		sprintf_s(str, sizeof(str), "%10s (%7.2fK)", insertCommas(size), static_cast<float>(size) / 1024.0f);
	else
		sprintf_s(str, sizeof(str), "%10s bytes     ", insertCommas(size));

	return str;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




mld_impl::mld_impl()
	: m_currentAllocations(0)
	, m_breakId(0)
	, m_totalAllocations(0)
	, m_mismatchPolicy(policy::break_)
	, m_neverAllocatedPolicy(policy::ignore)
	, m_mutex(CreateMutex(0, false, L"MLD Container Mutex"))
{
	// Make sure the mutex was created successfully
	if(!m_mutex)
		throw std::runtime_error("Unable to create mutexfor the MLD");
}
///////////////////////////////////////////////////////////////////////////////////////////////////

mld_impl::~mld_impl()
{
	CloseHandle(m_mutex);
	m_mutex = 0;

	dumpLeakReport();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void* mld_impl::allocator(alloc::type type, size_t size, const char* module, const char* file, const char* function, uint32 line)
{
	WaitForSingleObject(m_mutex, INFINITE);

	// Increase our allocation count
	m_currentAllocations++;

	// We HAVE to keep a copy of the increased alloc count for our Id,
	// because the id can be increased in another thread after the mutex
	// has been released
	std::size_t allocNumber = m_totalAllocations++;
	ReleaseMutex(m_mutex);

	// If you hit this assert, you requested a breakpoint on a specific allocation count
	if(m_totalAllocations == m_breakId)
	{
		OutputDebugStringA("User defined allocation number was hit\n");
		m_break();
	}

	alloc_unit au;

	au.size     = size;
	au.address  = malloc(au.size);
	au.type     = type;
	au.line     = line;
	au.id       = allocNumber;
	au.module   = module;
	au.file     = file;
	au.function = function;


	if(0 == au.address)
		return 0;


	WaitForSingleObject(m_mutex, INFINITE);
	m_allocUnits.insert(au);
	ReleaseMutex(m_mutex);

	return au.address;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::deallocator(alloc::type type, void* address)
{
	// Go get the allocation unit
	WaitForSingleObject(m_mutex, INFINITE);
	alloc_map::iterator i = m_allocUnits.find(address);

	// If you hit this assert, you tried to deallocate RAM that wasn't allocated by this memory manager.
	if(i == m_allocUnits.end())
	{
		switch(m_neverAllocatedPolicy)
		{
		case policy::break_:
			m_break();
			break;
		case policy::report:
			OutputDebugStringA("Error: Request to deallocate RAM that was never allocated with mld_impl\n");
			break;
		}

		// Bugfix: Release the mutex if we return from the function right here
		ReleaseMutex(m_mutex);

		return;
	}

	alloc_unit au = m_allocUnits.deref(i);

	// Go delete the allocation unit
	m_allocUnits.remove(i);
	m_currentAllocations--;

	ReleaseMutex(m_mutex);


	if((type == alloc::delete_       && au.type != alloc::new_     ) ||
		(type == alloc::delete_array && au.type != alloc::new_array))
	{
		switch(m_mismatchPolicy)
		{
		case policy::break_:
			m_break();
			break;
		case policy::report:
			OutputDebugStringA("Allocation/deallocation mismatch\n");
			break;
		}
	}

	// If you hit this assert, then the "break on dealloc" flag for this allocation unit is set. Interrogate the 'au'
	// variable to determine information about this allocation unit.
	if(au.break_)
	{
		OutputDebugStringA("Breaking on specified reallocation\n");
		m_break();
	}

	// Do the deallocation
	free(au.address);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::setMismatchPolicy(policy::type p)
{
	m_mismatchPolicy = p;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::setNeverAllocatedPolicy(policy::type p)
{
	m_neverAllocatedPolicy = p;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::dumpLeakReport()
{
	if(m_currentAllocations)
	{
		// First, output a nice header to inform the programmer that there are memory leaks
		char buffer[ 1024 ];
		sprintf_s(buffer, sizeof(buffer), "\n%d memory leak%s found:\n", m_currentAllocations, m_currentAllocations == 1 ? "":"s");
		OutputDebugStringA(buffer);

		// Now dump out every single leak
		dumpAllocations();

		// Then add a newline to separate the memory leak output from the rest of the console output
		OutputDebugStringA("\n");
	}
	else
	{
		OutputDebugStringA("\nCongratulations! No memory leaks found!\n");
	}

	char buffer[256];
	sprintf_s(buffer, sizeof(buffer), "(tracked %d dynamic memory allocations)\n\n", m_totalAllocations);
	OutputDebugStringA(buffer);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::dumpAllocations()
{
	OutputDebugStringA("Alloc.              Size              BreakOn              \n");
	OutputDebugStringA("Number  Address   Requested   Method  Dealloc Allocated by \n");
	OutputDebugStringA("------ ---------- ---------- -------- ------- --------------------------------------------------- \n");

	char buffer[1024];

	// We want to sort the results
	// This container automatically sorts the memory leaks
	alloc_vector allocUnits(m_allocUnits);

	for(alloc_vector::iterator i = allocUnits.begin(); i != allocUnits.end(); i++)
	{
		const alloc_unit& au = allocUnits.deref(i);

		sprintf_s(buffer, sizeof(buffer), "%06d 0x%08X 0x%08X %-08s    %c    %s\n",
					au.id,
					(UINT) au.address,
					au.size,
					alloc::toString(au.type),
					au.break_ ? 'Y':'N',
					ownerString(au.module, au.file, au.line, au.function));

		OutputDebugStringA(buffer);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::breakOnAlloc(size_t allocationCount)
{
	m_breakId = allocationCount;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void mld_impl::breakOnDealloc(void* reportedAddress, bool break_)
{
	WaitForSingleObject(m_mutex, INFINITE);

	// Locate the existing allocation unit
	alloc_map::iterator i = m_allocUnits.find(reportedAddress);

	// If you hit this assert, you tried to set a breakpoint on deallocation for an address that doesn't exist. Interrogate the
	// stack frame to find out what went wrong
	if(i == m_allocUnits.end())
	{
		OutputDebugStringA("Error: You cannot break on deallocation of memory, that was not allocated with mld_impl");
		m_break();
	}

	alloc_unit& au = m_allocUnits.deref(i);
	au.break_ = break_;

	ReleaseMutex(m_mutex);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
